
package ray.surface;

import java.util.ArrayList;
import ray.Intersection;
import ray.Ray;
import ray.math.*;
import ray.shader.Shader;

public abstract class Surface
{
	
    protected Matrix4 objToWorldCoords;

    /** The inverse of the transformation matrix. */
    protected Matrix4 worldToObjCoords;

    /** The inverse of the transpose of the transformation matrix. */
    protected Matrix4 objToWorldCoordsTransposeInv;
    protected Shader shader;
    protected Matrix4 tMat;
    protected Matrix4 tMatInv;
    protected Matrix4 tMatTInv;
    private Point3 t1p;
    private Point3 t2p;
    private double t1;
    private double t2;
    private double discr;
    protected Point3 averagePosition;
    protected Point3 minBound;
    protected Point3 maxBound;

    public Surface()
    {
        shader = Shader.DEFAULT_MATERIAL;
        t1p = new Point3();
        t2p = new Point3();
        averagePosition = new Point3();
        minBound = new Point3();
        maxBound = new Point3();
    }
    
    protected double findIntersection(Intersection outRecord, Ray ray){
    	return 1;  	
    }

    public void setShader(Shader material)
    {
        shader = material;
    }

    public Shader getShader()
    {
        return shader;
    }

    public Point3 getAveragePosition()
    {
        return averagePosition;
    }

    public Point3 getMinBound()
    {
        return minBound;
    }

    public Point3 getMaxBound()
    {
        return maxBound;
    }
    
    public void appendRenderableSurfaces(ArrayList<Surface> in) {
		in.add(this);
	}

    public abstract void computeBoundingBox();

    public abstract boolean intersect(Intersection intersectionrecord, Ray ray);

    public void addTo(ArrayList sceneSurfaces)
    {
        sceneSurfaces.add(this);
    }

    public void setTransformation(Matrix4 a, Matrix4 aInv, Matrix4 aTInv)
    {
    	a = Group.tmp;
        tMat = a;
        tMatInv = aInv;
        tMatTInv = aTInv;
        computeBoundingBox();
    }

    protected Ray untransformRay(Ray rayIn)
    {
        Ray ray = new Ray(rayIn.p, rayIn.d);
        ray.start = rayIn.start;
        ray.end = rayIn.end;
        tMatInv.rightMultiply(ray.d);
        tMatInv.rightMultiply(ray.p);
        return ray;
    }
    
    protected Matrix4 getObjToWorldTransform() {
        return tMat;
    }

    protected void transformBoundingBox()
    {
        Point3 boxPts[] = new Point3[8];
        for(int i = 0; i < 8; i++)
        {
            boxPts[i] = new Point3();
            boxPts[i].set(minBound);
        }

        Vector3 extent = new Vector3();
        extent.sub(maxBound, minBound);
        boxPts[1].x += extent.x;
        boxPts[2].y += extent.y;
        boxPts[3].z += extent.z;
        boxPts[4].x += extent.x;
        boxPts[4].y += extent.y;
        boxPts[5].x += extent.x;
        boxPts[5].z += extent.z;
        boxPts[6].y += extent.y;
        boxPts[6].z += extent.z;
        boxPts[7].x += extent.x;
        boxPts[7].y += extent.y;
        boxPts[7].z += extent.z;
        for(int i = 0; i < 8; i++)
            tMat.rightMultiply(boxPts[i]);

        Point3 newMinBound = new Point3();
        Point3 newMaxBound = new Point3();
        newMinBound.set(boxPts[0]);
        newMaxBound.set(boxPts[0]);
        for(int i = 1; i < 8; i++)
        {
            for(int c = 0; c < 3; c++)
            {
                if(boxPts[i].getE(c) < newMinBound.getE(c))
                    newMinBound.setE(c, boxPts[i].getE(c));
                if(boxPts[i].getE(c) > newMaxBound.getE(c))
                    newMaxBound.setE(c, boxPts[i].getE(c));
            }

        }

        minBound.set(newMinBound);
        maxBound.set(newMaxBound);
        averagePosition.set(minBound);
        for(int c = 0; c < 3; c++)
            averagePosition.setE(c, minBound.x + (maxBound.x - minBound.x) / 2D);

    }

    public void addTo(ArrayList sceneSurfaces, ArrayList groups)
    {
        sceneSurfaces.add(this);
    }

    protected boolean is_valid_tpoint(Point3 tpt)
    {
        return true;
    }

    protected boolean checkQuadraticIntersection(Intersection outRecord, Ray rayIn, double a, double b, double c)
    {
        discr = b * b - 4D * a * c;
        if(discr < 0.0D)
            return false;
        t1 = (-b + Math.sqrt(discr)) / (2D * a);
        t1p.set(rayIn.p);
        t1p.scaleAdd(t1, rayIn.d);
        boolean t1isValid = t1 > rayIn.start && t1 < rayIn.end && is_valid_tpoint(t1p);
        if(discr == 0.0D)
        {
            if(t1isValid)
            {
                outRecord.intersection.set(t1p);
                outRecord.t = t1;
            } else
            {
                return false;
            }
        } else
        {
            t2 = (-b - Math.sqrt(discr)) / (2D * a);
            t2p.set(rayIn.p);
            t2p.scaleAdd(t2, rayIn.d);
            boolean t2isValid = t2 > rayIn.start && t2 < rayIn.end && is_valid_tpoint(t2p);
            if(!t1isValid && !t2isValid)
                return false;
            if(t1isValid)
            {
                if(!t2isValid || t2isValid && t1 < t2)
                {
                    outRecord.intersection.set(t1p);
                    outRecord.t = t1;
                } else
                {
                    outRecord.intersection.set(t2p);
                    outRecord.t = t2;
                }
            } else
            {
                outRecord.intersection.set(t2p);
                outRecord.t = t2;
            }
        }
        outRecord.surface = this;
        return true;
    }
}
